package com.insight.config.shiro.realm;

import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
import com.yuanqiao.insight.common.util.common.LocalCacheUtils;
import com.yuanqiao.insight.common.util.common.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import com.insight.common.api.CommonAPI;
import com.insight.common.constant.CommonConstant;
import com.insight.common.system.vo.LoginUser;
import com.insight.common.util.SpringContextUtils;
import com.insight.common.util.oConvertUtils;
import com.insight.config.shiro.token.OAuth2Token;
import com.insight.config.shiro.utils.ShiroUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Slf4j
@Component("oauth2Realm")
@ConditionalOnProperty(name = "jeecg.shiro.realm")
public class OAuth2Realm extends AuthorizingRealm {
    @Resource
    private CommonAPI commonAPI;
    @Resource
    private RedisUtils redisUtil;
    @Value("${jeecg.shiro.realm}")
    private String realm;

    private static final LocalCacheUtils cacheUtils = LocalCacheUtils.getInstance();

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuth2Token;
    }

    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     * @param principals 身份信息
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (!StringUtils.equals(realm, "oauth2")) {
            return null;
        }
        log.info("===============Shiro权限认证开始============ [ roles、permissions]==========[OAUTH2 REALM]==========");
        String username = null;
        if (principals != null) {
            LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
            username = sysUser.getUsername();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> roleSet = commonAPI.queryUserRoles(username);
        info.addRoles(roleSet);
        Set<String> permissionSet = commonAPI.queryUserAuths(username);
        info.addStringPermissions(permissionSet);
        log.info("===============Shiro权限认证成功==============");
        return info;
    }

    /**
     * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
     * 也就是说验证用户输入的账号和密码是否正确，错误抛出异常
     *
     * @param auth 用户登录的账号密码信息
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        String platformType = (String) auth.getPrincipal();
        if (token == null) {
            log.info("————————OAuth2身份认证失败——————————IP地址:  {}", oConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
            throw new AuthenticationException("token为空!");
        }
        // 校验token有效性
        LoginUser loginUser = this.checkUserTokenIsEffect(token, platformType);
        return new SimpleAuthenticationInfo(loginUser, token, getName());
    }

    /**
     * 校验token的有效性
     *
     * @param token
     */
    public LoginUser checkUserTokenIsEffect(String token, String platformType) throws AuthenticationException {
        JSONObject jwtPayload = ShiroUtils.decodeJwt(token);
        String username = jwtPayload.getStr("user_name");
        if (username == null) {
            throw new AuthenticationException("token非法无效!");
        }
        // 查询用户信息
        LoginUser loginUser = commonAPI.getUserByName(username);
        if (loginUser == null) {
            throw new AuthenticationException("用户不存在!");
        }
        // 判断用户状态
        if (loginUser.getStatus() != 1) {
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        }
        // 校验token是否超时失效
        if (!jwtTokenRefresh(token, platformType)) {
            throw new AuthenticationException("Token失效，请重新登录!");
        }
        return loginUser;
    }

    /**
     * @return
     */
    public boolean jwtTokenRefresh(String token, String platformType) {
        try {
            if (token == null || platformType == null || cacheUtils == null || redisUtil == null) {
                return false;
            }
            com.alibaba.fastjson.JSONObject clientId = JSON.parseObject(String.valueOf(Optional.ofNullable(cacheUtils.getValue("oauth2_clientId")).orElse("{}")));
            com.alibaba.fastjson.JSONObject clientSecret = JSON.parseObject(String.valueOf(Optional.ofNullable(cacheUtils.getValue("oauth2_clientSecret")).orElse("{}")));
            String ssoUrl = String.valueOf(Optional.ofNullable(cacheUtils.getValue("oauth2_ssoUrl")).orElse(""));
            String tokenInfoUri = String.valueOf(Optional.ofNullable(cacheUtils.getValue("oauth2_tokenInfoUri")).orElse(""));
            if (StringUtils.isBlank(ssoUrl) || StringUtils.isBlank(tokenInfoUri) || clientId == null || clientSecret == null) {
                return false;
            }
            String client = clientId.getString(platformType);
            String secret = clientSecret.getString(platformType);
            if (StringUtils.isBlank(client) || StringUtils.isBlank(secret)) {
                return false;
            }
            Map<String, Object> params = new HashMap<>();
            params.put("token", token);
            HttpResponse response = HttpRequest.post(String.format("%s%s", ssoUrl, tokenInfoUri))
                    .basicAuth(client, secret)
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
                    .form(params)
                    .execute();
            if (response != null && response.getStatus() == 200) {
                com.alibaba.fastjson.JSONObject tokenInfo = JSON.parseObject(response.body());
                if (tokenInfo != null && Boolean.TRUE.equals(tokenInfo.get("isValid"))) {
                    String expiresInStr = MapUtil.getStr(tokenInfo, "expiresIn");
                    if (StringUtils.isNotBlank(expiresInStr)) {
                        int expiresIn = Integer.parseInt(expiresInStr);
                        redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token, expiresIn);
                        return true;
                    }
                }
            }
        } catch (Exception e) {
            log.error("token校验失败", e);
            return false;
        }
        return false;
    }
}
